<?php
/**
 * 数据库处理器
 *
 * 基于PDO构建的简单接口层，用于兼容不同数据库和操作
 */

class DB {
	//当前的数据库实例对象
	public $db = null;

	/**
	 * 数据库属性设置函数
	 *
	 * 用于设置各方面的参数，如数据库连接是否持久化，数据输出格式等
	 *
	 * @param string $attrName 属性名 , mixed $attrVal 属性值
	 * @return void
	 */

	public function setAttr($attrName, $attrVal = null) {
		$this->db->_setAttr($attrName, $attrVal);
	}

	/**
	 * 数据库连接设置配置参数
	 *
	 * 用于设置最新的配置参数
	 *
	 * @param array 配置参数数组
	 * @return void
	 */

	public function setConfig($params) {
		$this->config = $params;
	}

	/**
	 * 数据库连接开启函数
	 *
	 * 用于连接对应数据库类型的数据库并返回存储的连接实例
	 *
	 * @param string $instanceName 数据库连接实例 , string $dbType 数据库类型 , bool $persistent 是否持久化连接 , array $params 传给连接的数据库的参数数组
	 * @return void 类中获得当前生成的数据库实例
	 */

	public function connect($persistent = false, $params = array()) {
		$dbType = strtolower($this->config['dbType']);
		if(!file_exists(SYSPATH . 'databases/' . $dbType . '.php')) {
			showError('error', '找不到数据库操作类', '系统无法在databases目录找到' . $dbType . '数据库操作类', 404);
		}
		$this->config['persistent'] = $persistent;
		//更新数据库配置数据
		foreach($params as $key => $val) {
			$this->config[$key] = $val;
		}
		$this->db = loadClass($dbType, null, SYSPATH . 'databases/');
		$this->db->_setConfig($this->config);
		//连接到数据库
		$this->db->_connect();
	}

	/**
	 * 数据库连接关闭函数
	 *
	 * 用于断开指定实例名称的数据库连接
	 *
	 * @param string $instanceName 要关闭的数据库连接实例名称
	 * @return void $this->db将获得一个null
	 */

	public function disconnect() {
		$this->db = null;
	}

	/**
	 * 数据库插入操作函数
	 *
	 * 用于执行当前数据库连接的删除操作
	 *
	 * @param string $table 表名称 , string $fields 要插入的字段 , array $data 插入的数据 , array $attrs 构造语句的属性
	 * @return int 插入了多少行
	 */

	public function insert($table, $fields = '', $data = array(), $attrs = array()) {
		$this->dbClosed(debug_backtrace());
		if(!isset($table)) {
			showError('error', '插入操作错误', '插入语句中未设定数据表');
		}
		$result = $this->db->_insert($table, $fields, $data, $attrs);
		return $result;
	}

	/**
	 * 数据库表数据复制操作函数
	 *
	 * 用于执行当前数据库连接的表数据复制操作
	 *
	 * @param string $insertTable 表名称 , string $insertFields 要插入的字段 , string $selectTable , string $selectFields , array $data 用于表数据复制操作的数据 , array $attrs 构造语句的属性
	 * @return int 复制了多少行
	 */

	public function insertFromTable($insertTable, $insertFields = '*', $selectTable, $selectFields = '*', $where = '', $data = array(), $attrs = array()) {
		$this->dbClosed(debug_backtrace());
		if(!isset($insertTable, $selectTable)) {
			showError('error', '插入操作错误', '插入语句中未设定被插入或进行插入的数据表');
		}
		$result = $this->db->_insertFromTable($insertTable, $insertFields, $selectTable, $selectFields, $where, $data, $attrs);
		return $result;
	}

	/**
	 * 数据库删除操作函数
	 *
	 * 用于执行当前数据库连接的删除操作
	 *
	 * @param string $table 表名称 , array $data 用于删除的数据 , array $attrs 构造函数的属性
	 * @return int 被删除了多少行
	 */

	public function delete($table, $where = '', $data = array(), $attrs = array()) {
		$this->dbClosed(debug_backtrace());
		if(!isset($table)) {
			showError('error', '删除操作错误', '删除语句中未设定数据表');
		}
		$result = $this->db->_delete($table, $where, $data, $attrs);
		return $result;
	}

	/**
	 * 数据库查询操作函数
	 *
	 * 用于执行当前数据库连接的查询操作
	 *
	 * @param string $table 表名称 ， string $fields 需要查询的字段 , array $data 用于查询的数据 , array $attrs 构造语句的属性 
	 * @return array 查询到的数据集合
	 */

	public function select($table, $fields = '*', $where = '', $data = array(), $format = 'AS-AL', $attrs = array()) {
		$this->dbClosed(debug_backtrace());
		if(!isset($table)) {
			showError('error', '查询操作错误', '查询语句中未设定数据表');
		}
		$result = $this->db->_select($table, $fields, $where, $data, $format, $attrs);
		return $result;
	}

	/**
	 * 数据库修改操作函数
	 *
	 * 用于执行当前数据库连接的修改操作
	 *
	 * @param string $table 表名称 , string $updateFields 需要修改的字段 , array $data 用于修改的数据 , array $attrs 构造语句的属性
	 * @return int 被修改了多少行数据
	 */

	public function update($table, $updateFields, $where, $data = array(), $attrs = array()) {
		$this->dbClosed(debug_backtrace());
		if(!isset($table)) {
			showError('error', '修改操作错误', '修改语句中未设定数据表');
		}
		$result = $this->db->_update($table, $updateFields, $where, $data, $attrs);
		return $result;
	}

	/**
	 * 数据库SQL语句执行函数
	 *
	 * 用于执行sql语句与参数绑定
	 *
	 * @param string $sql 要执行的SQL语句 , array $data 要绑定的参数 , string $format 返回数据的格式类型
	 * @return array/bool 执行结果数组或true
	 */

	public function query($sql = '', $data = array(), $format = 'AS-AL') {
		$this->dbClosed(debug_backtrace());
		if(empty($sql)) {
			showError('error', '数据库操作失败', 'query函数并未得到一个SQL语句，无法进行数据库操作');
		}
		//设置错误提示级别
		$this->setAttr(array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
		$sql = trim($sql, ';') . ';';
		$result = $this->db->_query($sql, $data, $format);
		return $result;
	}

	/**
	 * sql语句执行函数
	 *
	 * 直接执行语句
	 *
	 * @param string $sql SQL语句
	 * @return mixed 返回执行结果
	 */

	public function exec($sql) {
		$this->dbClosed(debug_backtrace());
		$sql = trim($sql, ';') . ';';
		return $this->db->db->exec($sql);
	}

	/**
	 * 上次操作数据ID获取
	 *
	 * 用于获得上次插入数据的该行ID值
	 *
	 * @param void
	 * @return int 上次插入的数据行id值
	 */

	public function lastInsertId() {
		$this->dbClosed(debug_backtrace());
		//直接访问数据库连接的该方法
		$lastInsertId = $this->db->db->lastInsertId();
		return $lastInsertId;
	}

	/**
	 * 数据库连接关闭提示函数
	 *
	 * 如果数据库关闭，此函数被执行时将出现数据库连接已关闭的错误
	 *
	 * @param object $backtrace 获得一个调用信息对象
	 * @return void 连接已关闭的错误页面
	 */

	private function dbClosed($backtrace) {
		if(is_null($this->db)) {
			$errorObj = $backtrace;
			$filePath = '[unknown]';
			$line = '[unknown]';
			if(isset($errorObj[0]) && isset($errorObj[0]['file'])) {
				$filePath = $errorObj[0]['file'];
				$line = $errorObj[0]['line'];
			}
			showError('error', '连接已关闭', '当前数据库连接已经被关闭，无法对数据库进行更多操作，你可以通过切换或新建数据库连接重新进行操作', 500, null, false, $filePath, $line);
		}
	}

	/**
	 * 数据条数统计函数
	 *
	 * 简易的数据条数统计函数
	 *
	 * @param array $result 数据数组
	 * @return int 数据条数
	 */

	public function count($result) {
		if(!is_array($result)) {
			return 0;
		}
		$num = 0;
		foreach($result as $key) {
			$num++;
		}
		return $num;
	}

	/**
	 * 通用数据格式化函数
	 *
	 * 使用此函数将执行结果用指定的模板输出相应结构数据
	 *
	 * @param object $readySql 执行完毕的结果对象 string $format 指定的数据模板名称 array $params 需要追加到已有数据中的数据数组
	 * @return object/array/bool 数据数组或对象或true(NO无返回)
	 */

	protected function fetch($readySql, $format, $params = array()) {
		$result = null;
		$format = explode('-', $format);
		$all = isset($format[1]) && $format[1] == 'AL';
		switch($format[0]) {
			case 'AS':
				//常规的数据获取，获得一个正常的表结构数组
				$result = $all ? $readySql->fetchAll(PDO::FETCH_ASSOC) : $readySql->fetch(PDO::FETCH_ASSOC);
			break;
			case 'NAMED':
				//将对应结果集中的每一行作为一个由列名索引的数组返回。如果结果集中包含多个名称相同的列，返回一个包含值的数组
				$result = $all ? $readySql->fetchAll(PDO::FETCH_ASSOC) : $readySql->fetch(PDO::FETCH_ASSOC);
			break;
			case 'NUM':
				//返回一个按顺序排号的数据数组
				$result = $all ? $readySql->fetchAll(PDO::FETCH_NUM) : $readySql->fetch(PDO::FETCH_NUM);
			break;
			case 'LAZY':
				//将结果集中的每一行作为一个对象返回，此对象的变量名对应着列名
				$result = $readySql->fetch(PDO::FETCH_NUM);
			break;
			case 'COL':
				//从结果集中的下一行返回所需要的那一列
				$result = $all ? $readySql->fetchAll(PDO::FETCH_COLUMN) : $readySql->fetch(PDO::FETCH_COLUMN);
			break;
			case 'BOTH':
				//将对应结果集中的每一行作为一个由列号和列名索引的数组返回，从第 0 列开始
				$result = $all ? $readySql->fetchAll(PDO::FETCH_BOTH) : $readySql->fetch(PDO::FETCH_BOTH);
			break;
			case 'OBJ':
				//将结果集中的每一行作为一个属性名对应列名的对象返回
				$result = $all ? $readySql->fetchAll(PDO::FETCH_OBJ) : $readySql->fetch(PDO::FETCH_OBJ);
			break;
			case 'BOUND':
				//返回 TRUE 且将结果集中的列值分配给通过 PDOStatement::bindParam() 或 PDOStatement::bindColumn() 方法绑定的 PHP 变量
				$result = $all ? $readySql->fetchAll(PDO::FETCH_BOUND) : $readySql->fetch(PDO::FETCH_BOUND);
			break;
			case 'CLASS':
				//返回一个所请求类的新实例，映射列到类中对应的属性名
				$readySql->setFetchMode(PDO::FETCH_CLASS, $params['className']);
				$result = $all ? $readySql->fetchAll() : $readySql->fetch();
			break;
			case 'INTO':
				//更新一个请求类的现有实例，映射列到类中对应的属性名
				$result = $all ? $readySql->fetchAll(PDO::FETCH_INTO) : $readySql->fetch(PDO::FETCH_INTO);
			break;
			case 'LINE':
				//获得影响的行数
				$result = $readySql->rowCount();
			break;
		}
		return $result;
	}

	/**
	 * 数据库错误处理函数
	 *
	 * 使用此函数可以对不同数据库的错误信息进行处理
	 *
	 * @param string $dbType 数据库类型 , object $error PDO异常对象
	 * @return void 输出错误页面
	 */

	protected function errorHandle($dbType, $error) {
		$errorObj = debug_backtrace();
		$dbType = strtolower($dbType);
		//获取是否开启了调试模式
		$debug = getConfigItem('debug');
		$errorCode  = $error->getCode();
		$filePath = '[unknown]';
		$line = '[unknown]';
		$msg = $error->getMessage();
		if(isset($errorObj[0]) && isset($errorObj[0]['file'])) {
			$filePath = $errorObj[0]['file'];
			$line = $errorObj[0]['line'];
		}
		if($debug == true) {
			$errorText = &$this->getErrorText($dbType, $errorCode, $msg);
			showError('error', $dbType . '数据库操作失败', $errorText, 500, null, false, $filePath, $line);
		}
		else {
			showError('error', '数据库操作失败', $msg, 500, 'default', false, $filePath, $line);
		}
	}

	/**
	 * 数据库错误代码解释获取函数
	 *
	 * 使用此函数可以对不同数据库的错误信息进行对应解释处理
	 *
	 * @param string $dbType 数据库类型 , object $errorCode 错误代码
	 * @return string 错误代码对应的解释
	 */

	private function &getErrorText($dbType, $errorCode, $msg = '') {
		static $errorTextList = array();
		$errorText = '';
		if(isset($errorTextList[$dbType]) && isset($errorTextList[$dbType][$errorCode])) {
			$errorText = $errorTextList[$dbType][$errorCode];
		}
		if(file_exists(CONFPATH . $dbType . 'ErrorTextList.php')) {
			require CONFPATH . $dbType . 'ErrorTextList.php';
			$errorTextList[$dbType] = ${$dbType . "ErrorTextList"};
			if(isset($errorTextList[$dbType][$errorCode])) {
				$errorText = "[{$errorCode}]" . $errorTextList[$dbType][$errorCode];
			}
		}
		else {
			showError('error', '错误检索失败', "找不到{$dbType}的错误列表文件");
		}
		if(preg_match_all('/\d+/', $msg, $match)) {
			$match = $match[0];
			foreach($match as $val) {
				if($val != $errorCode && isset($errorTextList[$dbType][$val])) {
					$errorText .= "[{$val}]" . $errorTextList[$dbType][$val] . ' ';
				}
			}
		}
		if(!empty($errorText)) {
			$errorText .= ' - ' . $msg;
		}
		else {
			$errorText = '错误列表中未定义的错误: ' . $msg;
		}
		return $errorText;
	}
}